#ifdef __ANDROID__
#include "ARenderer.h"
#include <android/native_window_jni.h>
#include <android/looper.h>
#include "AApp.h"
#include "AUtil.h"
#include "../../MgrController.h"
#include "../../SQ/SQUtil.h"
#include "../../core/CMath.h"
#include "../../test/TestCase.h"

P TestCase::s_debugModelPos;

static const char* BuildTime = "2025-07-15";
ASurfaceTexture* ARenderer::s_surfaceTexture = nullptr;
EGLDisplay ARenderer::s_eglDisplay = EGL_NO_DISPLAY;
EGLSurface ARenderer::s_eglSurface = EGL_NO_SURFACE;
EGLContext ARenderer::s_eglContext = EGL_NO_CONTEXT;

static int s_frameCnt = 0;

struct DebugStatus {
	bool printFPS = false;
	bool printRecvRenderTimeConsume = false;
	bool makeCrashEvery5s = false;
};
static const DebugStatus DebugInfo;

static long s_modelRecvTimeMs = -1;
static int s_modelRecvNO = -1;

static int RestartTimes = -1;

/**@note in MyRenderThread*/
void ARenderer::createEGLContext(ASurfaceTexture* surfaceTexture) {
	auto ok = AApp::attachCurrentThread();
	ASSERT(ok, "Failed to attachCurrentThread!");

	ANativeWindow* nativeWindow = ASurfaceTexture_acquireANativeWindow(surfaceTexture);

	EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
	ASSERT(display != EGL_NO_DISPLAY, "Failed to get EGL display!");

	EGLint major, minor;
	EGLBoolean initSuccess = eglInitialize(display, &major, &minor);
	ASSERT(initSuccess, "Failed to initialize EGL!");

	std::vector<EGLint> configAttribs;
	configAttribs.insert(configAttribs.end(), {
			EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, // request OpenGL ES 3.x
			EGL_RED_SIZE, 8,        // red-channel: 8bit
			EGL_GREEN_SIZE, 8,      // green-channel: 8bit
			EGL_BLUE_SIZE, 8,       // blue-channel: 8bit
			EGL_ALPHA_SIZE, 8,      // alpha-channel: 8bit
			EGL_DEPTH_SIZE, 24,     // depth-buffer-channel: 24bit
	});
	int multiSamples = Config::Render_SurfaceSamples;
	if (multiSamples > 1) {
		configAttribs.insert(configAttribs.end(), {
				EGL_SAMPLE_BUFFERS, 1,		// enable multisampling
				EGL_SAMPLES, multiSamples,  // 4x multisampling
		});
	}
	configAttribs.push_back(EGL_NONE);// always terminate with EGL_NONE

	EGLConfig config;
	EGLint numConfigs;
	eglChooseConfig(display, configAttribs.data(), &config, 1, &numConfigs);
	ASSERT(numConfigs > 0, "Failed to choose EGL config!")

	EGLSurface eglSurface = eglCreateWindowSurface(display, config, nativeWindow, nullptr);
	ASSERT(eglSurface != EGL_NO_SURFACE, "Failed to create EGL Window Surface!")

	const EGLint attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };// request OpenGL ES 3.x version
	EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, attribs);
	ASSERT(context != EGL_NO_CONTEXT, "Failed to create EGL context!");

	EGLBoolean success = eglMakeCurrent(display, eglSurface, eglSurface, context);
	ASSERT(success, "Failed to make current for EGL surface!");

	s_surfaceTexture = surfaceTexture;
	s_eglDisplay = display;
	s_eglSurface = eglSurface;
	s_eglContext = context;

	int surfaceW = ANativeWindow_getWidth(nativeWindow);
	int surfaceH = ANativeWindow_getHeight(nativeWindow);
	Config::resize3DView(surfaceW, surfaceH);

	Scene::init(new AGLProcLoader);
	LogI << "BuildInfo: " << BuildTime << ", " << Config::Version_Code;
	LogI << "GL Renderer and Scene is initialized successfully!       [restartTimes: " << ++RestartTimes << "]";

	AApp::registerSignalHandler();
	try {
	startRendering();
	} catch (const std::runtime_error& e) {
		LogE << "Caught SignalException: " << e.what() << "\n";
	}

	MgrController::releaseAll();
	destroyEGL();

	AApp::detachCurrentThread();
	Scene::switchRenderingStatus(false);
}

/**@note in MyRenderThread*/
void ARenderer::startRendering() {
	TestCase::run();

	while (Scene::isRendering()) {
		int modelRecvNO = s_modelRecvNO;
		MgrController::updateAllEntities();

		swapBuffer();

		CUtil::msleep(1);// proactively give up CPU time slices

		printDebugInfo(modelRecvNO);
	}
}

void ARenderer::destroyEGL() {
	if (s_eglDisplay != EGL_NO_DISPLAY) {
		eglMakeCurrent(s_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
		eglDestroySurface(s_eglDisplay, s_eglSurface);
		eglDestroyContext(s_eglDisplay, s_eglContext);
		eglTerminate(s_eglDisplay);
		s_eglDisplay = EGL_NO_DISPLAY;
		s_eglSurface = EGL_NO_SURFACE;
		s_eglContext = EGL_NO_CONTEXT;
		ASurfaceTexture_release(s_surfaceTexture);
	}
	LogI << "ARenderer:: EGL Surface is destroyed!!!      [Scene::isRendering() = " << std::boolalpha << Scene::isRendering() << "]";
}

void ARenderer::printDebugInfo(int modelRecvNO) {
	if (++s_frameCnt % 600 == 0)// log every 10s on average
		LogI << "frame cnt = " << s_frameCnt;
	
	if (Config::Released)
		return;
		
	if (DebugInfo.makeCrashEvery5s) {
		if (s_frameCnt % 300 == 0) {// crash every 5s
			LogE << "crashing xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (manually)";
			int *i;
			*i = 100;
		}
	}
	if (DebugInfo.printRecvRenderTimeConsume) {
		if (modelRecvNO == s_modelRecvNO)
			LOG_PRINT_I("ARenderer", "NO: #%d, Time consumes(from Recv to Render-Done): %dms",
						s_modelRecvNO, (int) (CUtil::currentTimeMillis() - s_modelRecvTimeMs));
	}

	if (DebugInfo.printFPS) {
	static CElapsedTimer timer;
	static int GapFrameN = 10;
	static int total = 0;
	static int i = 0;
	auto elapsed = timer.elapsed();
	//LOG_PRINT_I("renderNext", "%d +++++++++++++++ elpased:%d", i, elapsed);
	timer.reset();
	total += elapsed;
	if (++i == GapFrameN) {
		LOG_PRINT_I("renderNext", "************************ FPS: %.1fms", (float) total / GapFrameN);
		i = 0;
		total = 0;
		}
	}
}


/**@note in MainThread, create egl context in a new thread*/
extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_createEGLContext(JNIEnv* env, jclass /* jclz */, jobject surfaceTexture) {
	//global_surfaceTexture = env->NewGlobalRef(surfaceTexture);// make it accessible in different threads
	auto surfaceTex = ASurfaceTexture_fromSurfaceTexture(env, surfaceTexture);// note: has arg 'env', and every time coming a new surfaceTexture
	CUtil::doInBgThread(ARenderer::createEGLContext, surfaceTex);
}

extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_destroyEGLContext(JNIEnv* env, jclass /* jclz */) {
	Scene::switchRenderingStatus(false);
	LogI << "calling Java_com_hb_sq3d_Viewer3D_destroyEGLContext called! Scene::isRendering()=" << std::boolalpha << Scene::isRendering();
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_hb_sq3d_Viewer3D_isRendering(JNIEnv* env, jclass /* jclz */) {
	return Scene::isRendering();
}

/**@note in MainThread*/
extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_setReceivingDataEnabled(JNIEnv* env, jclass /* jclz */, jboolean enabled) {
	Cache::setReceivingData(enabled);
	LogI << "enable receiving data: " << std::boolalpha << (bool)enabled;
}

/**@note in MainThread*/
extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_onViewportChanged(JNIEnv *env, jclass /* jclz */, jint width, jint height) {
	Config::resize3DView(width, height);
	LogI << "onViewportChanged => width x height = " << width << " x " << height;
}

/**@note in MainThread*/
extern "C" JNIEXPORT jint JNICALL
Java_com_hb_sq3d_Viewer3D_frameUpdate(JNIEnv *env, jclass /* jclz */) {
	const int FRAME_UPDATE_STATUS_ZHICHE_RENDERED_DONE = 0x100;// keep same as Viewer3D.java
	return (ZhicheEntity::hasRenderedDone() ? 1 : -1) * FRAME_UPDATE_STATUS_ZHICHE_RENDERED_DONE;
}

extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_recvModels(JNIEnv *env, jclass clazz, jobject allModelArgs, jlong timeMs, jint number) {
	if (!Scene::isRendering()) return;
	s_modelRecvTimeMs = timeMs;
	s_modelRecvNO = number;
	auto idsPair = Get_IntArray_Ptr(allModelArgs, "ids");
	auto typesPair = Get_IntArray_Ptr(allModelArgs, "types");
	auto hDistsPair = Get_FloatArray_Ptr(allModelArgs, "hDists");
	auto vDistsPair = Get_FloatArray_Ptr( allModelArgs, "vDists");
	auto anglesPair = Get_FloatArray_Ptr( allModelArgs, "angles");

	auto idsPtr = idsPair.key;
	auto typesPtr = typesPair.key;
	auto hDistsPtr = hDistsPair.key;
	auto vDistsPtr = vDistsPair.key;
	auto anglesPtr = anglesPair.key;

	jsize N = env->GetArrayLength(idsPair.value);
	CVector<sq::ModelArg> modelArgs;
	CVector<int> ids;
	for (int i = 0; i < N; i++) {
		sq::ModelArg model;
		model.id = idsPtr[i];
		if (ids.contains(model.id))
			continue;
		if (idsPtr[i] < 1 || idsPtr[i] > 20)
			continue;
		if (typesPtr[i] < 0 || typesPtr[i] > 6)// 7 8 9 is for alert window
			continue;
		if (vDistsPtr[i] < -20 || vDistsPtr[i] > 200)
			continue;
		if (hDistsPtr[i] < -50 || hDistsPtr[i] > 50)
			continue;
		if (anglesPtr[i] < -180 || anglesPtr[i] > 180)
			continue;
		ids << model.id;
		model.type = SQUtil::modelType(typesPtr[i]);
		model.pos = SQUtil::modelPos(hDistsPtr[i], vDistsPtr[i]);
		model.angle = anglesPtr[i];
		modelArgs << model;
	}

	RELEASE_ARRAY(idsPair, typesPair, hDistsPair, vDistsPair, anglesPair);

	CVector<ModelArg> ktModels;
	SQUtil::toKuantiData(ktModels, modelArgs);
	Cache::setModelsValidForSize(ktModels);
	Cache::recvModels(ktModels);
}

extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_recvLines(JNIEnv *env, jclass /* jclz */, jobject allLineArgs) {
	if (!Scene::isRendering()) return;
	auto idsPair = Get_IntArray_Ptr(allLineArgs, "ids");
	auto stylesPair = Get_IntArray_Ptr(allLineArgs, "styles");
	auto coloursPair = Get_IntArray_Ptr(allLineArgs, "colours");
	auto eqCsPair = Get_Float2DArray_Ptr(allLineArgs, "Cs");
	auto eqStartsPair = Get_FloatArray_Ptr(allLineArgs, "starts");
	auto eqEndsPair = Get_FloatArray_Ptr(allLineArgs, "ends");

	auto idsPtr = idsPair.key;
	auto stylesPtr = stylesPair.key;
	auto coloursPtr = coloursPair.key;
	auto eqCsPtr = eqCsPair.key;
	auto eqStartsPtr = eqStartsPair.key;
	auto eqEndsPtr = eqEndsPair.key;

	jsize N = env->GetArrayLength(idsPair.value);
	CVector<sq::LineArg> lineArgs;
	CVector<int> ids;
	for (int i = 0; i < N; i++) {
		int rawId = idsPtr[i];
		if (ids.contains(rawId))
			continue;
		if (rawId < 0 || rawId > 4)
			continue;
		if (stylesPtr[i] < 1 || stylesPtr[i] > 10)
			continue;
		if (coloursPtr[i] < 0 || coloursPtr[i] > 1)
			continue;
		if (eqCsPtr[i][0] < -50 || eqCsPtr[i][0] > 50)
			continue;
		if (eqCsPtr[i][1] < -100 || eqCsPtr[i][1] > 100)
			continue;
		if (eqCsPtr[i][2] < -0.25 || eqCsPtr[i][2] > 0.25)
			continue;
		if (eqCsPtr[i][3] < -0.002 || eqCsPtr[i][3] > 0.002)
			continue;
		ids << rawId;
		sq::LineArg line;
		line.tag = SQUtil::rawId2Tag(idsPtr[i]);
		if (LineTag::None == line.tag)
			continue;
		line.style = SQUtil::lineStyle(stylesPtr[i]);
		line.color = SQUtil::lineColor(coloursPtr[i]);
		line.eq.C = CVector<float>(eqCsPtr[i], eqCsPtr[i] + 4);
		line.eq.zStart = -eqStartsPtr[i];
		line.eq.zEnd = -eqEndsPtr[i];
		lineArgs << line;
	}

	RELEASE_ARRAY(idsPair, stylesPair, coloursPair, eqStartsPair, eqEndsPair);
	RELEASE_2DARRAY(eqCsPair);

	CVector<LineArg> ktLineArgs;
	SQUtil::toKuantiData(ktLineArgs, lineArgs);
	Cache::recvLines(ktLineArgs);
}

extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_recvStatus(JNIEnv *env, jclass /* jclz */, jobject status) {
	if (!Scene::isRendering()) return;
	sq::Status status;
	status.acc = SQUtil::acc(Get_IntValue(status, "acc"));
	status.fcw = SQUtil::fcw(Get_IntValue(status, "fcw"));
	status.alc = SQUtil::alc(Get_IntValue(status, "alc"));
	status.noa = SQUtil::noa(Get_IntValue(status, "noa"));
	status.acas = SQUtil::acas(Get_IntValue(status, "acas"));
	Global::setZhicheSpeed(Get_IntValue(status, "zhicheSpeed"));
	//Global::setZhicheAngle(CMath::radians(Get_FloatValue(status, "zhicheAngle")));
	Cache::updateSQStatus(status);
}

extern "C" JNIEXPORT void JNICALL
Java_com_hb_sq3d_Viewer3D_changeDarkMode(JNIEnv *env, jclass /* jclz */, jboolean isDarkMode) {
	if (!Scene::isRendering()) return;
	LogI << "isDarkMode: " << std::boolalpha << (bool)isDarkMode;
	Global::setDarkMode(isDarkMode);
}

#endif